package net.sf.fmj.ejmf.toolkit.media;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.media.CachingControl;
import javax.media.CachingControlEvent;
import javax.swing.JButton;
import javax.swing.JProgressBar;

/**
 * This class provides a CachingControl for the AbstractController class. It
 * provides a progress bar to monitor the media download. Its control Component
 * is simply a button that stops the media download. Whenever there is a change
 * in this BasicCachingControl, a CachingControlEvent is posted automatically.
 * 
 * From the book: Essential JMF, Gordon, Talley (ISBN 0130801046). Used with
 * permission.
 * 
 * @see net.sf.fmj.ejmf.toolkit.media.AbstractController
 * 
 * @author Steve Talley & Rob Gordon
 */
public class BasicCachingControl implements CachingControl {
	private final static String PAUSEMESSAGE = "Pause";
	private final static String RESUMEMESSAGE = "Resume";

	private boolean isDownloading;
	private boolean isPaused;
	private long length;
	private long progress;
	private JProgressBar progressBar;
	private JButton pauseButton;
	private AbstractController controller;

	/**
	 * Constructs a BasicCachingControl for the given AbstractController. Uses
	 * the length arg to initialize the progress bar.
	 * 
	 * @param c
	 *            The AbstractController from which CachingControlEvents are
	 *            posted whenever the status of this BasicCachingControl
	 *            changes.
	 * 
	 * @param length
	 *            The length of the media to be downloaded.
	 */
	public BasicCachingControl(AbstractController c, long length) {
		controller = c;
		progressBar = new JProgressBar();
		progressBar.setMinimum(0);

		pauseButton = new JButton(PAUSEMESSAGE);

		pauseButton.addActionListener(new ActionListener() {
			/**
			 * Stops and deallocates the AbstractController when the stop button
			 * is pushed.
			 */
			public void actionPerformed(ActionEvent e) {
				String label = pauseButton.getText();

				if (isPaused()) {
					pauseButton.setText(PAUSEMESSAGE);
					setPaused(false);
				} else {
					pauseButton.setText(RESUMEMESSAGE);
					setPaused(true);
				}

				// Resize button to fit new label
				pauseButton.getParent().validate();
			}
		});

		reset(length);
		controller.addControl(this);
	}

	/**
	 * Sets whether the AbstractController is paused or not.
	 * 
	 * @param isPaused
	 *            boolean indicating whether the media is paused.
	 */
	protected synchronized void setPaused(boolean isPaused) {
		this.isPaused = isPaused;
		notifyAll();
	}

	/**
	 * Tells whether the media download is paused or not.
	 */
	public boolean isPaused() {
		return isPaused;
	}

	/**
	 * Blocks the current thread until the download is not paused.
	 */
	public synchronized void blockWhilePaused() {
		while (isPaused) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}
	}

	/**
	 * Sets whether the AbstractController is downloading or not. Other methods
	 * in the BasicCachingControl call this method automatically.
	 * 
	 * @param isDownloading
	 *            boolean indicating whether the media is downloading.
	 */
	public void setDownLoading(boolean isDownloading) {
		this.isDownloading = isDownloading;
		if (isDownloading == false) {
			setPaused(false);
		}
	}

	/**
	 * Resets this BasicCachingControl. Sets the media length, reinitializes the
	 * progress bar, and posts a CachingControlEvent.
	 * 
	 * @param length
	 *            The length of the media.
	 */
	public synchronized void reset(long length) {
		this.length = length;

		progress = 0;
		progressBar.setValue(0);

		setContentLength(length);
		setDownLoading(false);
		setPaused(false);

		// Post CachingControlEvent
		controller
				.postEvent(new CachingControlEvent(controller, this, progress));
	}

	/**
	 * Set the length of the media without reinitializing the progress bar.
	 * 
	 * @param length
	 *            The length of the media.
	 */
	public synchronized void setContentLength(long length) {
		this.length = length;
		if (length == LENGTH_UNKNOWN) {
			progressBar.setMaximum(0);
		} else {
			progressBar.setMaximum((int) length);
		}
	}

	/**
	 * Set the current progress of the media download.
	 * 
	 * @param progress
	 *            Number of bytes that have been downloaded.
	 */
	public synchronized void setContentProgress(long progress) {
		blockWhilePaused();
		this.progress = progress;
		setDownLoading(progress < length);

		progressBar.setValue((int) progress);

		// Post CachingControlEvent
		controller
				.postEvent(new CachingControlEvent(controller, this, progress));

		// Uncomment following line to slow down download enough
		// for this CachingControl to be useful
		// try { Thread.sleep(500); } catch( InterruptedException e) {}
	}

	/**
	 * Adds an amount of bytes to the media already downloaded.
	 * 
	 * @param toAdd
	 *            Number of bytes that have been downloaded since the last time
	 *            this method or setContentProgress have been called.
	 */
	public void addToProgress(long toAdd) {
		setContentProgress(progress + toAdd);
	}

	/**
	 * Indicates that the media is fully downloaded.
	 */
	public void setDone() {
		setContentProgress(length);
	}

	// //////////////////////////////////////////////////////
	//
	// javax.media.CachingControl methods
	//
	// //////////////////////////////////////////////////////

	/**
	 * Check whether or not media is being downloaded.
	 */
	public boolean isDownloading() {
		return isDownloading;
	}

	/**
	 * Get the total number of bytes in the media being downloaded. Returns
	 * LENGTH_UNKNOWN if this information is not available.
	 * 
	 * @return The media length in bytes, or LENGTH_UNKNOWN.
	 */
	public long getContentLength() {
		return length;
	}

	/**
	 * Get the total number of bytes of media data that have been downloaded so
	 * far.
	 * 
	 * @return The number of bytes downloaded.
	 */
	public long getContentProgress() {
		return progress;
	}

	/**
	 * Get a Component for displaying the download progress.
	 * 
	 * @return Progress bar GUI Component.
	 */
	public Component getProgressBarComponent() {
		return progressBar;
	}

	/**
	 * Get a Component that provides additional download control. Returns null
	 * if only a progress bar is provided.
	 * 
	 * @return Download control GUI Component.
	 */
	public Component getControlComponent() {
		return pauseButton;
	}
}
